package org.bukkitmodders.copycat.listeners;

import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.imageio.ImageIO;
import javax.vecmath.Matrix4d;

import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerListener;
import org.bukkitmodders.copycat.Plugin;
import org.bukkitmodders.copycat.RevertableBlock;
import org.bukkitmodders.copycat.managers.ConfigurationManager;
import org.bukkitmodders.copycat.managers.PlayerSettingsManager;
import org.bukkitmodders.copycat.schema.PlayerSettingsType.Shortcuts.Shortcut;
import org.bukkitmodders.copycat.util.ImageCopier;
import org.bukkitmodders.copycat.util.ImageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CopyCatPlayerListener extends PlayerListener {

	private final Logger log = LoggerFactory.getLogger(CopyCatPlayerListener.class);
	private final Plugin plugin;

	private final ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

	public CopyCatPlayerListener(Plugin instance) {
		plugin = instance;
	}

	@Override
	public void onPlayerCommandPreprocess(final PlayerChatEvent event) {

		String commandPrefix = "/" + plugin.getDescription().getName().toLowerCase();

		if (event.getMessage().startsWith(commandPrefix)) {

			final String command = event.getMessage().replace(commandPrefix, "").trim();

			threadPool.submit(new Runnable() {

				public void run() {
					try {
						performCommand(command, event);
					} catch (Exception e) {
						log.error("Error processing command", e);
					}
				}
			});
		} 
	}

	private void performCommand(String commandString, PlayerChatEvent event) throws Exception {
		StringTokenizer st = new StringTokenizer(commandString);

		String command = st.nextToken();

		if (command.equalsIgnoreCase("copy")) {
			doCopy(st, event);
		} else if (command.equalsIgnoreCase("undo")) {
			doUndo(st, event);
		} else if (command.equalsIgnoreCase("help")) {
			doHelp(event);
		} else if (command.equalsIgnoreCase("add")) {
			doAdd(st, event);
		} else if (command.equalsIgnoreCase("delete")) {
			doDelete(st, event);
		} else if (command.equalsIgnoreCase("enable")) {
			doEnable(st, event);
		} else if (command.equalsIgnoreCase("disable")) {
			doDisable(st, event);
		} else if (command.equalsIgnoreCase("enableWorld")) {
			doEnableWorld(st, event);
		} else if (command.equalsIgnoreCase("disableWorld")) {
			doDisableWorld(st, event);
		} else if (command.equalsIgnoreCase("list")) {
			doList(st, event);
		} else {
			event.getPlayer().sendMessage("Unrecognized CopyCat Command: " + command);
			doHelp(event);
		}
	}

	private void doList(StringTokenizer st, PlayerChatEvent event) {

		ConfigurationManager configurationManager = plugin.getConfigurationManager();
		Player player = event.getPlayer();
		PlayerSettingsManager playerSettings = configurationManager.getPlayerSettings(player, player.getName());

		playerSettings.tellShortcuts(player);
	}

	private void doDisableWorld(StringTokenizer st, PlayerChatEvent event) {

		if (!event.getPlayer().isOp()) {
			event.getPlayer().sendMessage("You are not an OP");
			return;
		}

		String worldName = st.nextToken();

		ConfigurationManager configurationManager = plugin.getConfigurationManager();
		configurationManager.disableWorld(worldName);

		event.getPlayer().sendMessage("World Disabled");
	}

	private void doEnableWorld(StringTokenizer st, PlayerChatEvent event) {
		if (!event.getPlayer().isOp()) {
			event.getPlayer().sendMessage("You are not an OP");
			return;
		}

		String worldName = st.nextToken();

		ConfigurationManager configurationManager = plugin.getConfigurationManager();
		configurationManager.enableWorld(worldName);

		event.getPlayer().sendMessage("World Enabled");
	}

	private void doDisable(StringTokenizer st, PlayerChatEvent event) {

		String targetPlayer = st.nextToken();
		Player player = event.getPlayer();

		if (player.isOp()) {
			PlayerSettingsManager playerSettings = plugin.getConfigurationManager().getPlayerSettings(player,
					targetPlayer);
			playerSettings.disable();
		} else {
			player.sendMessage("You're not an OP");
		}
	}

	private void doEnable(StringTokenizer st, PlayerChatEvent event) {

		String targetPlayer = st.nextToken();
		Player player = event.getPlayer();

		if (player.isOp()) {
			PlayerSettingsManager playerSettings = plugin.getConfigurationManager().getPlayerSettings(player,
					targetPlayer);
			playerSettings.enable();
		} else {
			player.sendMessage("You're not an OP");
		}

	}

	private void doDelete(StringTokenizer st, PlayerChatEvent event) {
		String shortcutName = st.nextToken();

		Player player = event.getPlayer();
		PlayerSettingsManager playerSettings = plugin.getConfigurationManager().getPlayerSettings(player,
				player.getName());
		playerSettings.deleteShortcut(shortcutName);

		player.sendMessage("Shortcut deleted");
	}

	private void doAdd(StringTokenizer st, PlayerChatEvent event) {

		String shortcutName = st.nextToken();
		String shortcutUrl = st.nextToken();

		Player player = event.getPlayer();
		PlayerSettingsManager playerSettings = plugin.getConfigurationManager().getPlayerSettings(player,
				player.getName());

		playerSettings.addShortcut(shortcutName, shortcutUrl);

		player.sendMessage("Shortcut added");
	}

	private void doHelp(PlayerChatEvent event) {

		Player player = event.getPlayer();

		player.sendMessage("Valid Commands are: ");

		if (player.isOp()) {
			player.sendMessage("enable <player name>");
			player.sendMessage("disable <player name>");
			player.sendMessage("enableWorld <world name>");
			player.sendMessage("disableWorld <world name>");
		}

		player.sendMessage("copy <shortcut name | URL >");
		player.sendMessage("add <shortcut name> <URL>");
		player.sendMessage("delete <shortcut name> <URL>");
		player.sendMessage("list");
		player.sendMessage("undo");
		player.sendMessage("help");
	}

	private void doUndo(StringTokenizer st, PlayerChatEvent event) {

		log.debug(event.getPlayer() + " performing undo");

		Player player = event.getPlayer();
		HashMap<String, Stack<Stack<RevertableBlock>>> undoBuffers = plugin.getUndoBuffers();

		String playerName = player.getName();
		if (undoBuffers.containsKey(playerName) && !undoBuffers.get(playerName).isEmpty()) {

			Stack<RevertableBlock> revertableBlocks = undoBuffers.get(playerName).pop();

			while (!revertableBlocks.isEmpty()) {
				revertableBlocks.pop().revert();
			}
		} else {
			player.sendMessage("Nothing to Undo");
		}
	}

	private void doCopy(StringTokenizer st, PlayerChatEvent event) throws FileNotFoundException, IOException {

		ConfigurationManager configurationManager = plugin.getConfigurationManager();
		Player player = event.getPlayer();
		PlayerSettingsManager playerSettings = plugin.getConfigurationManager().getPlayerSettings(player,
				player.getName());

		if (!playerSettings.isEnabled()) {
			player.sendMessage("You are disabled");
			return;
		}

		if (!configurationManager.isWorldEnabled(player.getWorld().getName())) {
			player.sendMessage("Copy disabled for this world");
			return;
		}

		log.debug(player + " performing copy");

		String resource = st.nextToken();

		Block targetBlock = player.getTargetBlock(null, 100);

		Shortcut shortcut = playerSettings.getShortcut(resource);
		if (shortcut != null) {
			resource = shortcut.getUrl();
		}

		BufferedImage image = ImageIO.read(new URL(resource));
		image = scaleImage(image);

		player.sendMessage("Copying your image: " + resource);
		player.sendMessage("Width: " + image.getWidth() + " Height: " + image.getHeight());
		Stack<RevertableBlock> undoBuffer = createUndoBuffer(player);

		Location location = new Location(player.getWorld(), targetBlock.getX(), targetBlock.getY(), targetBlock.getZ());

		Matrix4d orientationMatrix = calculateOrientation(player, targetBlock);
		ImageCopier mcGraphics2d = new ImageCopier(location, player.getWorld(), orientationMatrix);
		mcGraphics2d.draw(image, undoBuffer);
	}

	private BufferedImage scaleImage(BufferedImage image) {

		ConfigurationManager cm = plugin.getConfigurationManager();

		int maxImageWidth = cm.getMaxImageWidth();
		int maxImageHeight = cm.getMaxImageHeight();

		int newWidth = Math.min(image.getWidth(), maxImageWidth);
		int newHeight = Math.min(image.getHeight(), maxImageHeight);

		return ImageUtil.scaleImage(image, image.getType(), newWidth, newHeight);
	}

	private Stack<RevertableBlock> createUndoBuffer(Player player) {
		Stack<RevertableBlock> undoBuffer = new Stack<RevertableBlock>();
		HashMap<String, Stack<Stack<RevertableBlock>>> undoBuffers = plugin.getUndoBuffers();
		String playerName = player.getName();

		if (!undoBuffers.containsKey(playerName)) {
			undoBuffers.put(playerName, new Stack<Stack<RevertableBlock>>());
		}

		undoBuffers.get(playerName).push(undoBuffer);
		return undoBuffer;
	}

	private Matrix4d calculateOrientation(Player player, Block block) {

		Matrix4d orientation = new Matrix4d();
		orientation.setIdentity();
		Matrix4d rotXZ = new Matrix4d();
		orientation.setIdentity();

		Location location = player.getLocation();

		double yaw = Math.abs(location.getYaw());

		yaw %= 360;

		double baseAngle = 0;
		int sign = (location.getYaw() < 0) ? -1 : 1;

		if (yaw > (90 - 45) & yaw < (90 + 45)) {
			baseAngle = 90;
		} else if (yaw > (180 - 45) & yaw < (180 + 45)) {
			baseAngle = 180;
		} else if (yaw > (270 - 45) && yaw < (270 + 45)) {
			baseAngle = 270;
		}

		if (sign < 0) {
			baseAngle = 360 - baseAngle;
		}

		double pitch = location.getPitch();
		boolean down = false;
		boolean up = false;
		// if (pitch > 45) {
		// down = true;
		// } else if (pitch < -45) {
		// up = true;
		// }

		if (baseAngle == 0 || baseAngle == 360) {
			if (down || up) {
				rotXZ.rotX(Math.toRadians(90));
				orientation.mul(rotXZ);
			} else {
				rotXZ.rotX(Math.toRadians(+90));
				orientation.mul(rotXZ);
				rotXZ.rotZ(Math.toRadians(-180));
				orientation.mul(rotXZ);
				rotXZ.rotX(Math.toRadians(-90));
				orientation.mul(rotXZ);
			}
		} else if (baseAngle == 90) {
			if (down || up) {
				rotXZ.rotZ(Math.toRadians(-90));
				orientation.mul(rotXZ);
				rotXZ.rotX(Math.toRadians(90));
				orientation.mul(rotXZ);
			} else {
				rotXZ.rotX(Math.toRadians(90));
				orientation.mul(rotXZ);
				rotXZ.rotZ(Math.toRadians(-90));
				orientation.mul(rotXZ);
				rotXZ.rotX(Math.toRadians(-90));
				orientation.mul(rotXZ);
			}
		} else if (baseAngle == 180) {
			if (down || up) {
				rotXZ.rotX(Math.toRadians(-90));
			}
		} else if (baseAngle == 270) {
			if (down || up) {
				rotXZ.rotZ(Math.toRadians(90));
				orientation.mul(rotXZ);
				rotXZ.rotX(Math.toRadians(-90));
				orientation.mul(rotXZ);
			} else {
				rotXZ.rotX(Math.toRadians(-90));
				orientation.mul(rotXZ);
				rotXZ.rotZ(Math.toRadians(-90));
				orientation.mul(rotXZ);
				rotXZ.rotX(Math.toRadians(90));
				orientation.mul(rotXZ);
			}
		}

		return orientation;
	}
}
